/*
 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ohoscodecdata.h"
#include "ohosvideodecoder.h"
#include <thread>

#include "hilog/log.h"
#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0x3200 // 全局domain宏，标识业务领域
#define LOG_TAG "MY_TAG"  // 全局tag宏，标识模块日志tag


namespace ohos {

CodecData::CodecData()
{
}

CodecData::~CodecData()
{
    Release();
}

int32_t CodecData::InputData(const uint8_t *data, size_t size, OH_AVCodecBufferAttr *attr, int *indx,
                             std::chrono::milliseconds waitTime)
{
    if (attr == nullptr) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "OH_AVCodecBufferAttr can't be NULL!");
        return ERRCODE_PARAM_INVALID;
    }

    std::unique_lock<std::mutex> lock(inputMutex_);
    bool waitForTimeout = !inputCond_.wait_for(lock, waitTime, [this](){
        return !this->inputBufferQueue_.empty();
    });

    if (waitForTimeout) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "inputBufferQueue wait %{public}lld ms timeout!", waitTime.count());
        return ERRCODE_TIMEOUT;
    }
    if (inputBufferQueue_.empty()) {
        OH_LOG_Print(LOG_APP, LOG_WARN, LOG_DOMAIN, "DecoderData", "inputBufferInfoQueue is empty");
        return ERRCODE_QUEUE_EMPTY;
    }

    AvBufferInfo bufferInfo = inputBufferQueue_.front();
    inputBufferQueue_.pop();
    inputFrameCount_++;
    lock.unlock();

    bufferInfo.Attr(attr);

    if (data != nullptr && size > 0) {
        uint8_t *avBufferAddr = bufferInfo.AvBufferAddr();
        // 判断avbuffer大小，循环写入
        if (avBufferAddr == nullptr || size > bufferInfo.AvBufferSize()) {
            OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "AV buffAddr is nullptr");
            return ERRCODE_UNKNOWN;
        }
        memcpy(avBufferAddr, data, size);
    }

    if (indx != nullptr) {
        *indx = bufferInfo.Index();
    }

    if (attr->flags & AVCODEC_BUFFER_FLAGS_EOS) {
        OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "DecoderData", "Catch EOS");
    }

    return ERRCODE_OK;
}

int32_t CodecData::OutputData(AvBufferInfo &buff, std::chrono::milliseconds waitTime)
{
    std::unique_lock<std::mutex> lock(this->outputMutex_);
    bool waitForTimeout = !outputCond_.wait_for(lock, waitTime, [this](){
        return !this->outputBufferQueue_.empty();
    });

    if (waitForTimeout && outputBufferQueue_.empty()) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "outputBufferQueue wait %{public}lld ms timeout!", waitTime.count());
        return ERRCODE_TIMEOUT;
    }

    if (outputBufferQueue_.empty()) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "outputBufferQueue_ is empty");
        return ERRCODE_QUEUE_EMPTY;
    }

    buff = outputBufferQueue_.front();
    outputBufferQueue_.pop();
    outputFrameCount_++;
    return ERRCODE_OK;
}

int32_t CodecData::GetOutputBuffer(AvBufferInfo &buff, std::chrono::milliseconds waitTime)
{
    std::unique_lock<std::mutex> lock(this->outputMutex_);
    bool waitForTimeout = !outputCond_.wait_for(lock, waitTime, [this](){
        return !this->outputBufferQueue_.empty();
    });

    if (waitForTimeout && outputBufferQueue_.empty()) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "outputBufferQueue wait timeout!");
        return ERRCODE_TIMEOUT;
    }

    if (outputBufferQueue_.empty()) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "outputBufferQueue_ is empty");
        return ERRCODE_QUEUE_EMPTY;
    }
    buff = outputBufferQueue_.front();
    outputBufferQueue_.pop();
    outputFrameCount_++;
    return ERRCODE_OK;
}

int32_t CodecData::GetInputBuffer(AvBufferInfo &buff, std::chrono::milliseconds waitTime)
{
    std::unique_lock<std::mutex> lock(this->inputMutex_);
    bool waitForTimeout = !inputCond_.wait_for(lock, waitTime, [this](){
        return !this->inputBufferQueue_.empty();
    });

    if (waitForTimeout && inputBufferQueue_.empty()) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "inputBufferQueue wait timeout!");
        return ERRCODE_TIMEOUT;
    }

    if (inputBufferQueue_.empty()) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "DecoderData", "inputBufferQueue is empty");
        return ERRCODE_QUEUE_EMPTY;
    }

    buff = inputBufferQueue_.front();
    inputBufferQueue_.pop();
    inputFrameCount_++;
    return ERRCODE_OK;
}

int32_t CodecData::NeedInputBuffer(uint32_t index, OH_AVBuffer *buffer)
{
    std::unique_lock<std::mutex> lock(inputMutex_);
    inputBufferQueue_.emplace(index, buffer);
    inputCond_.notify_all();

    return 0;
}

int32_t CodecData::NewOutputBuffer(uint32_t index, OH_AVBuffer *buffer)
{
    std::unique_lock<std::mutex> lock(outputMutex_);
    outputBufferQueue_.emplace(index, buffer);
    outputCond_.notify_all();

    return 0;
}

uint32_t CodecData::OutputFrame() const
{
    return outputFrameCount_;
}

void CodecData::Release()
{
    inputFrameCount_ = 0;
    outputFrameCount_ = 0;

    while (!inputBufferQueue_.empty()) {
        inputBufferQueue_.pop();
    }
    while (!outputBufferQueue_.empty()) {
        outputBufferQueue_.pop();
    }
}
} // namespace ohos
